namespace SimpleWebServices.Web.UI.Html.Elements
{
    using System;
    using System.Collections.Generic;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Xml.XPath;
    using SimpleWebServices;
    using SimpleWebServices.Data;
    using SimpleWebServices.Models;
    using SimpleWebServices.Web.UI.Html.Validation;
    using Xunit;

    public sealed class FormFacts
    {
        [Fact]
        public void typedef()
        {
            Assert.True(new ObjectExpectations<Form>()
                .DerivesFrom<InnerElements>()
                .IsConcreteClass()
                .IsUnsealed()
                .HasDefaultConstructor()
                .Implements<IXmlSerializable>()
                .XmlRoot("form")
                .Result);
        }

        [Fact]
        public void deserialize()
        {
            Form expected = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextString("Some text"),
                    new TextInput("data1"),
                    new TextInput("data2")
                }
            };

            string xml =
                "<form action='/action' method='post'>" +
                    "Some text" +
                    "<input id='data1' name='data1' type='text' />" +
                    "<input id='data2' name='data2' type='text' />" +
                "</form>";
            Form actual = xml.Deserialize<Form>();

            Assert.Equal<string>(expected.ToString(), actual.ToString());
        }

        [Fact]
        public void deserializeEmpty()
        {
            Assert.NotNull("<form />".Deserialize<Form>());
        }

        [Fact]
        public void serialize()
        {
            Form obj = new Form
            {
                Id = "id",
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput("data")
                }
            };

            XPathNavigator navigator = obj.Serialize().CreateNavigator();

            Assert.True((bool)navigator.Evaluate("1=count(/form[@action='/action'][@method='post']/input[@id='data'][@type='text'][@value=''])"));
        }

        [Fact]
        public void ctor()
        {
            Assert.NotNull(new Form());
        }

        [Fact]
        public void prop_Action()
        {
            Assert.True(new PropertyExpectations(typeof(Form).GetProperty("Action"))
                .IsAutoProperty<Uri>()
                .Result);
        }

        [Fact]
        public void prop_ErrorCount_getInvalidControl()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput
                    {
                        Value = "invalid",
                        Validator = new StandardTextValidator(new Label(), 1, 2)
                    }
                }
            };

            Assert.Equal<int>(1, form.ErrorCount);
        }

        [Fact]
        public void prop_ErrorCount_getInvalidFieldset()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput
                            {
                                Value = "invalid",
                                Validator = new StandardTextValidator(new Label(), 1, 2)
                            }
                        }
                    },
                    new TextInput
                    {
                        Value = "invalid",
                        Validator = new StandardTextValidator(new Label(), 1, 2)
                    }
                }
            };

            Assert.Equal<int>(2, form.ErrorCount);
        }

        [Fact]
        public void prop_ErrorCount_getValidControl()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput
                    {
                        Value = "valid",
                        Validator = new StandardTextValidator(new Label(), 2, 10)
                    }
                }
            };

            Assert.Equal<int>(0, form.ErrorCount);
        }

        [Fact]
        public void prop_ErrorCount_getValidFieldset()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput
                            {
                                Value = "valid",
                                Validator = new StandardTextValidator(new Label(), 2, 10)
                            }
                        }
                    }
                }
            };

            Assert.Equal<int>(0, form.ErrorCount);
        }

        [Fact]
        public void prop_IsValid_getInvalidControl()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput
                    {
                        Value = "invalid",
                        Validator = new StandardTextValidator(new Label(), 1, 2)
                    }
                }
            };

            Assert.False(form.IsValid);
        }

        [Fact]
        public void prop_IsValid_getInvalidFieldset()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput
                            {
                                Value = "invalid",
                                Validator = new StandardTextValidator(new Label(), 1, 2)
                            }
                        }
                    }   
                }
            };

            Assert.False(form.IsValid);
        }

        [Fact]
        public void prop_IsValid_getValidControl()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput
                    {
                        Value = "valid",
                        Validator = new StandardTextValidator(new Label(), 2, 10)
                    }
                }
            };

            Assert.True(form.IsValid);
        }

        [Fact]
        public void prop_IsValid_getValidFieldset()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput
                            {
                                Value = "valid",
                                Validator = new StandardTextValidator(new Label(), 2, 10)
                            }
                        }
                    }
                }
            };

            Assert.True(form.IsValid);
        }

        [Fact]
        public void prop_Method()
        {
            Assert.True(new PropertyExpectations(typeof(Form).GetProperty("Method"))
                .DefaultValueIs("post")
                .ArgumentNullException()
                .ArgumentOutOfRangeException(string.Empty)
                .ArgumentOutOfRangeException("delete")
                .Set("get")
                .ArgumentOutOfRangeException("head")
                .ArgumentOutOfRangeException("options")
                .Set("post")
                .ArgumentOutOfRangeException("put")
                .Result);
        }

        [Fact]
        public void indexer_string_get()
        {
            IElement expected = new TextInput("expected");

            Form form = new Form
            {
                Elements =
                {
                    new Fieldset
                    {
                        Elements = { expected }
                    }
                }
            };

            IElement actual = form["expected"];

            Assert.Same(expected, actual);
        }

        [Fact]
        public void indexer_stringMissing_get()
        {
            Assert.Throws<KeyNotFoundException>(() => new Form()["missing"]);
        }

        [Fact]
        public void indexer_stringNull_get()
        {
            Assert.Throws<ArgumentNullException>(() => new Form()[null as string]);
        }

        [Fact]
        public void indexer_stringEmpty_get()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => new Form()[new Name(string.Empty)]);
        }

        [Fact]
        public void op_Render()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("expect2") { TabIndex = 2 },
                            new TextInput("expect3")
                        }
                    },
                    new TextInput("expect4"),
                    new TextInput("expect1") { TabIndex = 1 }
                }
            };

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(form.Render());

            XmlNode node = xml.DocumentElement.SelectSingleNode("/form/fieldset/input[1]");
            Assert.Equal<string>("expect2", node.Attributes["id"].Value);
            Assert.Equal<string>("2", node.Attributes["tabindex"].Value);

            node = xml.DocumentElement.SelectSingleNode("/form/fieldset/input[2]");
            Assert.Equal<string>("expect3", node.Attributes["id"].Value);
            Assert.Null(node.Attributes["tabindex"]);

            node = xml.DocumentElement.SelectSingleNode("/form/input[1]");
            Assert.Equal<string>("expect4", node.Attributes["id"].Value);
            Assert.Null(node.Attributes["tabindex"]);

            node = xml.DocumentElement.SelectSingleNode("/form/input[2]");
            Assert.Equal<string>("expect1", node.Attributes["id"].Value);
            Assert.Equal<string>("1", node.Attributes["tabindex"].Value);
        }

        [Fact]
        public void op_Retrieve_Token()
        {
            FakeRepository.Clear();

            try
            {
                Form form = new Form
                {
                    Action = new Uri("/action", UriKind.Relative),
                    Method = "post",
                    Elements =
                    {
                        new Fieldset
                        {
                            Elements =
                            {
                                new TextInput("name1") { Value = "value1" }
                            }
                        },
                        new TextInput("name2") { Value = "value2" }
                    }
                };

                Token? token = Token.New();

                FakeRepository.Register<FormSession>(new
                {
                    token = token,
                    xml = 
                        "<session>" +
                            "<data>" +
                                "<value name='name1'>update1</value>" +
                                "<value name='name2'>update2</value>" +
                            "</data>" +
                        "</session>"
                });

                form.Retrieve(token);

                Assert.Equal<string>("update1", (form["name1"] as TextInput).Value);
                Assert.Equal<string>("update2", (form["name2"] as TextInput).Value);
            }
            finally
            {
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Retrieve_TokenNull()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("name1") { Value = "value1" }
                        }
                    },
                    new TextInput("name2") { Value = "value2" }
                }
            };

            Token? token = null;

            form.Retrieve(token);

            Assert.Equal<string>("value1", (form["name1"] as TextInput).Value);
            Assert.Equal<string>("value2", (form["name2"] as TextInput).Value);
        }

        [Fact]
        public void op_Retrieve_TokenEmpty()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("name1") { Value = "value1" }
                        }
                    },
                    new TextInput("name2") { Value = "value2" }
                }
            };

            form.Retrieve(Token.Empty);

            Assert.Equal<string>("value1", (form["name1"] as TextInput).Value);
            Assert.Equal<string>("value2", (form["name2"] as TextInput).Value);
        }

        [Fact]
        public void op_Retrieve_TokenUnknown()
        {
            FakeRepository.Clear();

            try
            {
                Form form = new Form
                {
                    Action = new Uri("/action", UriKind.Relative),
                    Method = "post",
                    Elements =
                    {
                        new Fieldset
                        {
                            Elements =
                            {
                                new TextInput("name1") { Value = "value1" }
                            }
                        },
                        new TextInput("name2") { Value = "value2" }
                    }
                };

                FakeRepository.Register<FormSession>();

                form.Retrieve(Token.New());

                Assert.Equal<string>("value1", (form["name1"] as TextInput).Value);
                Assert.Equal<string>("value2", (form["name2"] as TextInput).Value);
            }
            finally
            {
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Deserialize_string_string_string()
        {
            Form expected = new Form
            {
                Id = "id",
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new TextInput("data")
                }
            };

            string xml = 
                "<form id='id' action='/action' method='post'>" +
                    "<input id='data' name='data' type='text' value='' />" +
                "</form>";
            Form actual = new Form().Deserialize("form", null, xml) as Form;

            Assert.Equal<string>(expected.ToString(), actual.ToString());
        }

        [Fact]
        public void op_Store_new()
        {
            FakeRepository.Clear();

            try
            {
                Form form = new Form();

                IRepository sessions = FakeRepository.Register<FormSession>();

                DataCollection expected = new DataCollection();
                expected.Add("name", "value");

                Token? token = form.Store(expected);

                FormSession actualByToken = sessions.Select<FormSession>(token.Value);

                Assert.Equal<DataCollection>(expected, actualByToken.Data);
            }
            finally
            {
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Store_existing()
        {
            FakeRepository.Clear();

            try
            {
                Form form = new Form
                {
                    Action = new Uri("/action", UriKind.Relative),
                    Method = "post",
                    Elements =
                    {
                        new TextInput("name")
                    }
                };

                Token? token = Token.New();

                IRepository sessions = FakeRepository.Register<FormSession>(new
                {
                    token = token,
                    urn = FormSession.FormatUrn(token.Value),
                    xml = "<session><data><value name='name'>value</value></data></session>"
                });

                form.Retrieve(token);

                Assert.Equal<string>("value", (form.Elements[0] as IControl).Value);

                DataCollection expected = new DataCollection
                {
                    { "name", "updated" }
                };

                token = form.Store(expected);

                FormSession actualByToken = sessions.Select<FormSession>(token.Value);

                Assert.Equal<DataCollection>(expected, actualByToken.Data);
            }
            finally
            {
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Update_DataCollection()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("name1") { Value = "value1" }
                        }
                    },
                    new TextInput("name2") { Value = "value2" }
                }
            };

            FormSession session = new FormSession
            {
                Data = new DataCollection
                {
                    { "name1", "update1" },
                    { "name2", "update2" }
                }
            };

            form.Update(session);

            Assert.Equal<string>("update1", (form["name1"] as TextInput).Value);
            Assert.Equal<string>("update2", (form["name2"] as TextInput).Value);
        }

        [Fact]
        public void op_Update_DataCollectionEmpty()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("name1") { Value = "value1" }
                        }
                    },
                    new TextInput("name2") { Value = "value2" }
                }
            };

            form.Update(new DataCollection());

            Assert.Equal<string>("value1", (form["name1"] as TextInput).Value);
            Assert.Equal<string>("value2", (form["name2"] as TextInput).Value);
        }

        [Fact]
        public void op_Update_DataCollectionNull()
        {
            Form form = new Form
            {
                Action = new Uri("/action", UriKind.Relative),
                Method = "post",
                Elements =
                {
                    new Fieldset
                    {
                        Elements =
                        {
                            new TextInput("name1") { Value = "value1" }
                        }
                    },
                    new TextInput("name2") { Value = "value2" }
                }
            };

            form.Update(null as DataCollection);

            Assert.Equal<string>("value1", (form["name1"] as TextInput).Value);
            Assert.Equal<string>("value2", (form["name2"] as TextInput).Value);
        }

        [Fact]
        public void op_Update_DataCollection_whenFieldsetRadioInput()
        {
            Form form = (
                "<form>" +
                    "<fieldset id='first'>" +
                        "<input type='radio' id='first:last' name='first' value='last' />" +
                    "</fieldset>" +
                "</form>").Deserialize<Form>();

            FormSession session = new FormSession
            {
                Data = new DataCollection
                {
                    { "first", "last" }
                }
            };

            Assert.False((form["first:last"] as RadioInput).Checked);

            form.Update(session);

            Assert.True((form["first:last"] as RadioInput).Checked);
        }

        [Fact]
        public void op_Update_DataCollection_whenFieldsetCheckBoxInput()
        {
            Form form = (
                "<form>" +
                    "<fieldset id='first'>" +
                        "<input type='checkbox' id='first:last' name='first' value='last' />" +
                    "</fieldset>" +
                "</form>").Deserialize<Form>();

            FormSession session = new FormSession
            {
                Data = new DataCollection
                {
                    { "first", "last" }
                }
            };

            Assert.False((form["first:last"] as CheckBoxInput).Checked);

            form.Update(session);

            Assert.True((form["first:last"] as CheckBoxInput).Checked);
        }
    }
}